package com.moorror.cola.strategy.plugin.utils;

import com.google.common.collect.Maps;
import com.intellij.lang.jvm.types.JvmPrimitiveTypeKind;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.ClassUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.Map;

/**
 * @Author jiyanghuang
 * @Date 2022/3/13 1:52 PM
 */
public class ColaStrategyPluginPsiUtils {
    private ColaStrategyPluginPsiUtils() {
    }

    public static PsiMethod getEnclosingMethod(PsiFile psiFile, int position) {
        PsiElement psiElement = psiFile.findElementAt(position);
        return PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class);
    }

    public static boolean isInClassFile(PsiElement psiElement) {
        VirtualFile virtualFile = psiElement.getContainingFile().getVirtualFile();
        return virtualFile == null || virtualFile.getName().endsWith(".class");
    }

    public static boolean isInJarFileSystem(PsiElement psiElement) {
        if (psiElement == null)
            return false;
        VirtualFile virtualFile = psiElement.getContainingFile().getVirtualFile();
        if (virtualFile == null)
            return true;
        String protocol = virtualFile.getFileSystem().getProtocol();
        return protocol.equalsIgnoreCase("jar") || protocol.equalsIgnoreCase("zip");
    }

    public static VirtualFile findVirtualFile(PsiClass psiClass) {
        PsiFile containingFile = psiClass.getNavigationElement().getContainingFile();
        if (containingFile == null)
            return null;
        return containingFile.getVirtualFile();
    }

    public static PsiMethod findPsiMethod(PsiMethod[] psiMethods, String methodName, List<String> argTypes) {
        for (PsiMethod psiMethod : psiMethods) {
            if (ColaStrategyPluginPsiUtils.isMethod(psiMethod, methodName, argTypes))
                return psiMethod;
        }
        return null;
    }

    public static PsiMethod findPsiMethodSign(PsiMethod[] psiMethods, String methodName, String methodSign) {
        for (PsiMethod psiMethod : psiMethods) {
            if (ColaStrategyPluginPsiUtils.isMethod(psiMethod, methodName, methodSign))
                return psiMethod;
        }
        return null;
    }

    public static boolean isMethod(PsiMethod psiMethod, String methodName, List<String> argTypes) {
        if (!psiMethod.getName().equals(methodName))
            return false;
        PsiParameter[] psiParameters = psiMethod.getParameterList().getParameters();
        return parameterEquals(psiParameters, argTypes);
    }

    /**
     * 按方法签名来比较
     *
     * @param psiMethod
     * @param methodName
     * @param methodSign
     * @return
     */
    public static boolean isMethod(PsiMethod psiMethod, String methodName, String methodSign) {
        if (!psiMethod.getName().equals(methodName)) {
            return false;
        }
        String methodSignParser = MethodDescUtils.getMethodDescriptor(psiMethod);
        return methodSignParser.equals(methodSign);
    }

    public static boolean isAbstract(PsiClass psiClass) {
        return psiClass != null
                && (psiClass.isInterface()
                || psiClass.getModifierList() != null
                && psiClass.getModifierList().hasModifierProperty(PsiModifier.ABSTRACT)
        );
    }

    public static boolean isExternal(PsiClass psiClass) {
        return isInClassFile(psiClass) || isInJarFileSystem(psiClass);
    }

    public static PsiClass findPsiClass(PsiManager psiManager, String className) {
        return ClassUtil.findPsiClass(psiManager, className);
    }

    public static PsiMethod findPsiMethod(PsiManager psiManager,
                                          final String className, String methodName, List<String> argTypes) {
        PsiClass psiClass = ClassUtil.findPsiClass(psiManager, className);
        if (psiClass == null)
            return null;
        return findPsiMethod(psiClass, methodName, argTypes);
    }

    public static PsiMethod findPsiMethodSign(PsiManager psiManager,
                                              final String className, String methodName, String methodSign) {
        PsiClass psiClass = ClassUtil.findPsiClass(psiManager, className);
        if (psiClass == null)
            return null;
        return findPsiMethodSign(psiClass, methodName, methodSign);
    }

    @Nullable
    public static PsiMethod findPsiMethod(PsiClass psiClass, String methodName, List<String> argTypes) {
        PsiMethod[] psiMethods = psiClass.findMethodsByName(methodName, false);
        if (psiMethods.length == 0)
            return null;
        return ColaStrategyPluginPsiUtils.findPsiMethod(psiMethods, methodName, argTypes);
    }

    @Nullable
    public static PsiMethod findPsiMethodSign(PsiClass psiClass, String methodName, String methodSign) {
        PsiMethod[] psiMethods = psiClass.findMethodsByName(methodName, false);
        if (psiMethods.length == 0)
            return null;
        return ColaStrategyPluginPsiUtils.findPsiMethodSign(psiMethods, methodName, methodSign);
    }

    public static String getPackageName(PsiMethod psiMethod) {
        PsiElement psiElement = psiMethod.getParent();
        while (psiElement != null) {
            if (psiElement instanceof PsiJavaFile) {
                PsiJavaFile psiJavaFile = (PsiJavaFile) psiElement;
                return psiJavaFile.getPackageName();
            }
            psiElement = psiElement.getParent();
        }
        return null;
    }

    private static boolean parameterEquals(PsiParameter[] psiParameters, List<String> argTypes) {
        if (psiParameters.length != argTypes.size())
            return false;
        for (int i = 0; i < psiParameters.length; i++) {
            PsiParameter psiParameter = psiParameters[i];
            if (!psiParameter.getType().getCanonicalText().equals(argTypes.get(i)))
                return false;
        }
        return true;
    }

    /**
     * Find the best offset for navigation
     *
     * @param psiElement PsiElement like PsiClass, PsiMethod, PsiCallExpression etc.
     * @return the offset.
     */
    public static int findNaviOffset(PsiElement psiElement) {
        if (psiElement == null)
            return 0;
        int offset;
        if (psiElement instanceof PsiMethodCallExpression) {
            offset = psiElement.getFirstChild().getNavigationElement().getTextOffset();
        } else {
            offset = psiElement.getNavigationElement().getTextOffset();
        }
        return offset;
    }

    /**
     *
     * @param project
     * @param name
     * @return
     */
    public static PsiClass findPsiClass(Project project, String name){
        return ClassUtil.findPsiClass(psiManager(project), name);
    }

    public static PsiManager psiManager(Project project){
        return PsiManager.getInstance(project);
    }

    /**
     * 是否继承某个类或接口
     *
     * @param psiClass
     * @param clazz
     * @param project
     * @return
     */
    public static boolean isInheritor(PsiClass psiClass, Class<?> clazz, Project project) {
        if (psiClass.getQualifiedName().equals(clazz.getName())) {
            return false;
        }
        PsiClass psiClassParent = findPsiClass(project, clazz.getName());
        if (psiClassParent.isInterface()) {
            return psiClass.isInheritor(psiClassParent, true);
        }
        PsiClass psiClassP = psiClass.getSuperClass();
        while (!psiClassP.getQualifiedName().equals(Object.class.getName())) {
            if (psiClassP.getQualifiedName().equals(psiClassParent.getQualifiedName())) {
                return true;
            }
            psiClassP = psiClassP.getSuperClass();
        }
        return false;
    }

    /**
     * 是否继承某个类或接口
     *
     * @param psiClass
     * @param clazz
     * @param project
     * @return
     */
    public static boolean isInheritor(String fClassName, Class<?> clazz, Project project) {
        if (fClassName.equals(clazz.getName())) {
            return false;
        }
        PsiClass psiClass = findPsiClass(project, fClassName);
        PsiClass psiClassParent = findPsiClass(project, clazz.getName());
        if (psiClassParent.isInterface()) {
            return psiClass.isInheritor(psiClassParent, true);
        }
        PsiClass psiClassP = psiClass.getSuperClass();
        while (!psiClassP.getQualifiedName().equals(Object.class.getName())) {
            if (psiClassP.getQualifiedName().equals(psiClassParent.getQualifiedName())) {
                return true;
            }
            psiClassP = psiClassP.getSuperClass();
        }
        return false;
    }

    /**
     * 是否继承某个类或接口
     *
     * @param psiClass
     * @param clazz
     * @param project
     * @return
     */
    public static boolean isInheritor(String fClassName, String fClassNameParent, Project project) {
        if (fClassName.equals(fClassNameParent)) {
            return false;
        }
        PsiClass psiClass = findPsiClass(project, fClassName);
        PsiClass psiClassParent = findPsiClass(project, fClassNameParent);
        if (psiClassParent.isInterface()) {
            return psiClass.isInheritor(psiClassParent, true);
        }
        PsiClass psiClassP = psiClass.getSuperClass();
        while (!psiClassP.getQualifiedName().equals(Object.class.getName())) {
            if (psiClassP.getQualifiedName().equals(psiClassParent.getQualifiedName())) {
                return true;
            }
            psiClassP = psiClassP.getSuperClass();
        }
        return false;
    }

    public static String className(String fClassName){
        if(StringUtils.isBlank(fClassName)){
            return fClassName;
        }
        String[] pars = fClassName.split("\\.");
        return pars[pars.length - 1];
    }

    /**
     * 只完成了部分转换
     * @param psiClass
     * @return
     */
    public static Map<String, Object> psiClassFieldValue(PsiClass psiClass){
        Map<String, Object> re = Maps.newHashMap();
        for(PsiField psiField : psiClass.getFields()){
            if(psiField.getType() instanceof PsiPrimitiveType){
                PsiPrimitiveType primitiveType = (PsiPrimitiveType)psiField.getType();
                JvmPrimitiveTypeKind kind = primitiveType.getKind();
                if(JvmPrimitiveTypeKind.INT.equals(kind)){
                    re.put(psiField.getName(), Integer.valueOf(psiField.getInitializer().getText()));
                }else if(JvmPrimitiveTypeKind.BOOLEAN.equals(kind)){
                    re.put(psiField.getName(), Boolean.valueOf(psiField.getInitializer().getText()));
                }else if(JvmPrimitiveTypeKind.DOUBLE.equals(kind)){
                    re.put(psiField.getName(), Double.valueOf(psiField.getInitializer().getText()));
                }
            }
        }
        return re;
    }

    /**
     * @param psiElement 传入 PsiMethodCallExpression --> object.method() 如果是 object.method().method() 这种类型 PsiMethodCallExpression 时，无法解释也没有必要解释
     * @param times
     * @return object 所属的类型
     */
    public static PsiClass firstChildPsiReferenceExpression(PsiElement psiElement, int times) {
        if (times <= 0 || null == psiElement) {
            return null;
        }
        PsiElement current = psiElement;
        while (times > 0) {
            times--;
            PsiElement fc = current.getFirstChild();
            if (null != fc && fc instanceof PsiReferenceExpression) {
                current = fc;
                continue;
            }
            return null;
        }
        PsiType psiType = ((PsiReferenceExpression) current).getType();
        if(psiType instanceof PsiClassType){
            return ((PsiClassType)psiType).resolve();
        }
        return null;
    }

    /**
     * @param psiElement 传入 PsiMethodCallExpression --> object.method() 如果是 object.method().method() 这种类型 PsiMethodCallExpression 时，无法解释也没有必要解释
     * @param times
     * @return object 所属的类型
     */
    public static PsiClass firstChildPsiReferenceExpressionConstant(PsiElement psiElement) {
        if (null == psiElement) {
            return null;
        }
        // todo 看一下它的类型
        PsiElement fc = psiElement.getFirstChild();
        if (null != fc) {
            return (PsiClass) ((PsiReferenceExpression) fc).resolve();
        }
        return null;
    }

    public static PsiElement psiIdentifier(PsiElement psiElement) {
        if (null == psiElement) {
            return null;
        }

        if (psiElement instanceof PsiMethod) {
            for (PsiElement child : psiElement.getChildren()) {
                if (child instanceof PsiIdentifier) {
                    return child;
                }
            }
        } else if (psiElement instanceof PsiMethodCallExpression) {
            for (PsiElement child : psiElement.getFirstChild().getChildren()) {
                if (child instanceof PsiIdentifier) {
                    return child;
                }
            }
        }
        return null;
    }
}
